扩展-关于wav PCM格式的阐述
一、PCM和ADPCM
音频压缩
音频编码的出现很大程度上是为了压缩文件体积,大文件除了占用存储空间,对传输带宽也是一笔不小的消耗,这涉及到文件比特率的问题:音频越大,播放时在单位时间需读取的数据量也就越大,还是假设有一段 44.1k 采样率、16bit 位深的立体声 PCM 数据,播放 1 秒钟就需要 44100 × 2Byte × 2 = 172KB,意味着传输速率也必须达到 172KB/s,换算为码率就是 1376 kbps,对系统 IO 性能和数据可靠性都是不小的挑战。
如果事先对音频进行压缩编码,保存为 MP3 格式,能大幅度地降低音频数据量,同等参数下码率仅有 128kbps,传输起来要轻松的多。众多不同的音频压缩编码,采用的算法原理不尽相同,但有着同样的目的 —— 为了节约数据的存储和传输带宽。MP3 通过去除频域上人耳不敏感的部分减少数据量,包括霍夫曼编码、频谱重排列、IMDCT 变换等复杂过程,相比之下,ADPCM 编码格式就简单得多,解码器也更容易实现。
PCM是差分脉冲代码调制, 使用有损压缩格式时,某些数据在压缩过程中会更改和丢失。 ADPCM 是自适应查分脉冲编码调制,加入了自适应的差值数组,可以实现高达 4:1 的压缩比的同时,减少数据偏差。
ADPCM 格式
如果我们直接存储每一个点的16位的采样值,这样就需要53X16=848个位,大约是106字节。但我们换个思路,我们不存储采样值,而存储采样点两两之间的差值(采样值可能会很大,需要更多的位数来表达,比如16个位,但是两点之间一般来说是比较连续的,差值不会太大,所以这个差值只需要很少的几个位即可表达,比如4个位)。这样,我们只需要知道前一个点的值,又知道它与下一个点的差值,就可以计算得到下一个点了。这个差值就是所谓的“差分”!
这就是DPCM的主要思想精髓!!
“什么?DPCM?我们不是说ADPCM吗?”是的,DPCM是差分脉码调制,多一个A,就是自适应!为什么要自适应?
你有没有考虑到一个问题?如果取两点之差,这个差值超过了4个位可以表达的范围,该如何处理?音频信号虽然具有很高的连续性,但是我们并不能保证两点之间产生突变的可能!前面的差值可能是3、5、8、12,后面突然变成20、40,甚至更大,这时4个位还够用吗?数据量的减少,不能以数据丢失和失真作为代价!!
如果有一种方法,可以把两点之间的差值变换到固定的几个位即可表达的范围内,那就好了!而且这种变换是实时的,并且具有自适应性和预测能力的。这就是ADPCM的基本思想。它定义了一些因子,这种算法如果发现两点之间差值变大之后,就会用差值去和相应的因子作除法,从而减小了差值,让它可以减少到几个位可表达的数值范围内。而选择哪一个因子来除它,这就是ADPCM编码要作的事情了。
ADPCM算法巧妙的利用了音频信号的特点,也就是音频信号上的点与它前面的若干个点是有一定的相关性的,从而可以对下一个点进行预测,从而预先估计这个差值,从而选取相应的除数因子,去把差值归化到数值范围内!!以上就是ADPCM对音频信号进行编码大体思想和过程!
由于声音信号具有波形上的连续性,因此相邻两个采样值大小也非常接近,记录单个采样值通常需要 16bit,而记录前后两个采样点的差值(差分法),往往只需要 4bit,这便是 ADPCM 压缩编码的基本原理,因此通过 ADPCM 编码的音频文件,其大小只有 PCM 格式的四分之一。
不仅如此,ADPCM 的智能之处在于,对于变化剧烈的波形,算法通过自适应机制,能自动改变差分值的度量粒度,即使是抖动较大的信号,也可以保证前后采样差值总能用固定的 4bit 表示。在 PCM 编码的基础上增加 「差分」和「自适应」的特性,便是 ADPCM(Adaptive Differential Pulse Code Modulation 自适应差分脉冲编码调制) 名称的由来。
当然,ADPCM 算法实现简单、压缩率高的同时,必然要付出音质损失的代价 —— ADPCM 格式文件的声音听起来会略为粗糙,被同样是有损压缩的 MP3 编码吊打,不过用于提示音、人声讲话等场合还是绰绰有余。
实现 ADPCM 解码器
学习知识的一个好方法是动手实践,通过实际代码能加深对技术的理解,接下来将会介绍如何编写一个 ADPCM 解码器 —— 将 ADPCM 数据还原为 PCM 格式,但在开始之前,要先选定一个 ADPCM 标准作为依据。是的,ADPCM 下面还分了好几类,有 YAMAHA 、Microsoft、IMA 等标准,不同的处理流程也会有细分的规范。下面选择 Microsoft 格式(MS ADPCM)作为我们实现解码器的标准,并参考 FFmpeg 的代码,开启「照抄模式」。
Microsoft ADPCM 标准
有关 MS ADPCM 解码器的编程和实现,看懂这两个网页就够了:
- https://wiki.multimedia.cx/index.php/Microsoft_ADPCM
- https://ffmpeg.org/doxygen/3.1/adpcm_8c_source.html 第一个页面是 MultimediaWiki 对 Microsoft ADPCM 的基本介绍、技术细节及实现解码方式的详细描述,理解这些内容有助于我们看代码,做到心中有数,至少不会一脸懵逼。下面解释一下其中比较关键的信息。
block 构成
MS ADPCM 音频数据由许多 block(块)组成,每个块包含前言(preamble)和半字节序列(series of nibbles)两部分,preamble 包括一些自适应差分相关参数以及两个原始采样数据,半字节序列是编码后的 ADPCM 采样值序列。单个 block 的大小在 wav 头的 nBlockAlign 字段中表示。其中单声道和双声道的 preamble 格式又有些区别: 单声道 preamble 格式
byte 0 block predictor (builtin predictors are in the range [0..6] but others can be manually defined) bytes 1-2 initial delta bytes 3-4 sample 1 bytes 5-6 sample 2
立体声 preamble 格式
byte 0 left channel block predictor (should be [0..6]) byte 1 right channel block predictor (should be [0..6]) bytes 2-3 left channel initial idelta bytes 4-5 right channel initial idelta bytes 6-7 left channel sample 1 bytes 8-9 right channel sample 1 bytes 10-11 left channel sample 2 bytes 12-13 right channel sample 2
preamble 中主要包含了实现自适应差分机制相关的参数,除此除外在末尾还存储了两个原始采样值,这不难理解:由于差分过程是通过比较与前一值的差异得到当前值,因此需要两个初始采样(反映此 block 波形变化剧烈程度)作为基础,从而计算相关自适应参数,并解码半字节序列递推解出整一个 block 的 PCM 数据,依次处理所有 block 完成整个音频的解码。
数据解码
借助 preamble 中的参数变量外加三个表格,再经过一系列的运算便能解码 ADPCM 数据,得到完整 16bit PCM 格式音频,计算过程如下:
- predictor = ((sample1 * coeff1) + (sample2 * coeff2)) / 256
- predictor += (signed)nibble * delta (note that nibble is 2's complement)
- clamp predictor within signed 16-bit range
- PCM sample = predictor
- send PCM sample to the output
- shuffle samples: sample 2 = sample 1, sample 1 = calculated PCM sample
- compute next adaptive scale factor: delta = (AdaptationTable[nibble] * delta) / 256
- saturate delta to lower bound of 16
AdaptationTable、AdaptCoeff1 和 AdaptCoeff2三个表格存储了计算中用到的一些因子系数,页面中也给出了数组定义:
int AdaptationTable [] = {
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
} ;
// These are the 'built in' set of 7 predictor value pairs; additional values can be added to this table by including them as metadata chunks in the WAVE header
int AdaptCoeff1 [] = { 256, 512, 0, 192, 240, 460, 392 } ;
int AdaptCoeff2 [] = { 0, -256, 0, 64, 0, -208, -232 } ;
FFmpeg 代码解析
在网上能找到许多 ADPCM 的示例代码,但有许多人评论不能用、有杂音,这往往是编码标准没对上,或者使用姿势不正确,甚至代码本身就有问题。与其用零散的不规范例程,还不如直接去经典大项目里「抠代码」,规范性统一性和用法都有参考,FFmpeg 项目则是音视频编解码领域的「代码库」。
FFmpeg ADPCM 核心解码模块:FFmpeg: libavcodec/adpcm.c Source File
在文件末尾的宏函数列表中包含了 FFmpeg 所实现的所有 ADPCM 标准,其中的 AV_CODEC_ID_ADPCM_MS 是本文要实现的 MS 格式。
ADPCM_DECODER(AV_CODEC_ID_ADPCM_IMA_RAD, sample_fmts_s16, adpcm_ima_rad, "ADPCM IMA Radical");
ADPCM_DECODER(AV_CODEC_ID_ADPCM_IMA_SMJPEG, sample_fmts_s16, adpcm_ima_smjpeg, "ADPCM IMA Loki SDL MJPEG");
ADPCM_DECODER(AV_CODEC_ID_ADPCM_IMA_WAV, sample_fmts_s16p, adpcm_ima_wav, "ADPCM IMA WAV");
ADPCM_DECODER(AV_CODEC_ID_ADPCM_IMA_WS, sample_fmts_both, adpcm_ima_ws, "ADPCM IMA Westwood");
ADPCM_DECODER(AV_CODEC_ID_ADPCM_MS, sample_fmts_s16, adpcm_ms, "ADPCM Microsoft");
ADPCM_DECODER(AV_CODEC_ID_ADPCM_MTAF, sample_fmts_s16p, adpcm_mtaf, "ADPCM MTAF");
ADPCM_DECODER(AV_CODEC_ID_ADPCM_PSX, sample_fmts_s16p, adpcm_psx, "ADPCM Playstation");
adpcm.c
文件包含众多标准的处理,总代码量一千多行,便于分析起见,剔除没用到的代码,保留 MS ADPCM 相关部分,简化后的文件只有两百多行,核心函数是 adpcm_ms_expand_nibble()
和 adpcm_decode_frame():
static inline int16_t adpcm_ms_expand_nibble(ADPCMChannelStatus *c, int nibble)
{
int predictor;
predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 64;
predictor += ((nibble & 0x08)?(nibble - 0x10):(nibble)) * c->idelta;
c->sample2 = c->sample1;
c->sample1 = av_clip_int16(predictor);
c->idelta = (ff_adpcm_AdaptationTable[(int)nibble] * c->idelta) >> 8;
if (c->idelta < 16) c->idelta = 16;
if (c->idelta > INT_MAX/768) {
av_log(NULL, AV_LOG_WARNING, "idelta overflow\n");
c->idelta = INT_MAX/768;
}
return c->sample1;
}
adpcm_ms_expand_nibble()
函数按照 MultimediaWiki 中描述的解码流程,对 predictor、coeff 和 idelta 进行运算,将 4bit 的 ADPCM 数据 nibble 展开得到 16bit 原始 sample。
函数 adpcm_decode_frame()
则完成对 block 的处理,包括解析出 preamble 和 nibbles 序列数据、计算解码参数初始值、循环调用 adpcm_ms_expand_nibble()
完成解码,代码如下:
static int adpcm_decode_frame(AVCodecContext *avctx, void *data,
int *got_frame_ptr, AVPacket *avpkt)
{
AVFrame *frame = data;
const uint8_t *buf = avpkt->data;
int buf_size = avpkt->size;
ADPCMDecodeContext *c = avctx->priv_data;
ADPCMChannelStatus *cs;
int n, m, channel, i;
int16_t *samples;
int16_t **samples_p;
int st; /* stereo */
int count1, count2;
int nb_samples, coded_samples, approx_nb_samples, ret;
GetByteContext gb;
bytestream2_init(&gb, buf, buf_size);
nb_samples = get_nb_samples(avctx, &gb, buf_size, &coded_samples, &approx_nb_samples);
if (nb_samples <= 0) {
av_log(avctx, AV_LOG_ERROR, "invalid number of samples in packet\n");
return AVERROR_INVALIDDATA;
}
/* get output buffer */
frame->nb_samples = nb_samples;
if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
return ret;
samples = (int16_t *)frame->data[0];
samples_p = (int16_t **)frame->extended_data;
/* use coded_samples when applicable */
/* it is always <= nb_samples, so the output buffer will be large enough */
if (coded_samples) {
if (!approx_nb_samples && coded_samples != nb_samples)
av_log(avctx, AV_LOG_WARNING, "mismatch in coded sample count\n");
frame->nb_samples = nb_samples = coded_samples;
}
st = avctx->channels == 2 ? 1 : 0;
int block_predictor;
block_predictor = bytestream2_get_byteu(&gb);
if (block_predictor > 6) {
av_log(avctx, AV_LOG_ERROR, "ERROR: block_predictor[0] = %d\n",
block_predictor);
return AVERROR_INVALIDDATA;
}
c->status[0].coeff1 = ff_adpcm_AdaptCoeff1[block_predictor];
c->status[0].coeff2 = ff_adpcm_AdaptCoeff2[block_predictor];
if (st) {
block_predictor = bytestream2_get_byteu(&gb);
if (block_predictor > 6) {
av_log(avctx, AV_LOG_ERROR, "ERROR: block_predictor[1] = %d\n",
block_predictor);
return AVERROR_INVALIDDATA;
}
c->status[1].coeff1 = ff_adpcm_AdaptCoeff1[block_predictor];
c->status[1].coeff2 = ff_adpcm_AdaptCoeff2[block_predictor];
}
c->status[0].idelta = sign_extend(bytestream2_get_le16u(&gb), 16);
if (st){
c->status[1].idelta = sign_extend(bytestream2_get_le16u(&gb), 16);
}
c->status[0].sample1 = sign_extend(bytestream2_get_le16u(&gb), 16);
if (st) c->status[1].sample1 = sign_extend(bytestream2_get_le16u(&gb), 16);
c->status[0].sample2 = sign_extend(bytestream2_get_le16u(&gb), 16);
if (st) c->status[1].sample2 = sign_extend(bytestream2_get_le16u(&gb), 16);
*samples++ = c->status[0].sample2;
if (st) *samples++ = c->status[1].sample2;
*samples++ = c->status[0].sample1;
if (st) *samples++ = c->status[1].sample1;
for(n = (nb_samples - 2) >> (1 - st); n > 0; n--) {
int byte = bytestream2_get_byteu(&gb);
*samples++ = adpcm_ms_expand_nibble(&c->status[0 ], byte >> 4 );
*samples++ = adpcm_ms_expand_nibble(&c->status[st], byte & 0x0F);
}
if (avpkt->size && bytestream2_tell(&gb) == 0) {
av_log(avctx, AV_LOG_ERROR, "Nothing consumed\n");
return AVERROR_INVALIDDATA;
}
*got_frame_ptr = 1;
if (avpkt->size < bytestream2_tell(&gb)) {
av_log(avctx, AV_LOG_ERROR, "Overread of %d < %d\n", avpkt->size, bytestream2_tell(&gb));
return avpkt->size;
}
return bytestream2_tell(&gb);
}
照葫芦画瓢
把 adpcm_ms_expand_nibble()
和 adpcm_decode_frame()
两个函数的核心功能理解后,抽出主要代码,照葫芦画瓢实现我们自己的 ADPCM 解码器,这是我改的一个版本 lib_adpcm.c
,实现了单个 block 解码接口 adpcm_decode_block()
,同时简化了一些 FFmpeg 的代码写法,能达到同样的效果:
#include <stdint.h> #include <string.h> #include <limits.h>
#include "lib_adpcm.h"
/* These are for MS-ADPCM */
/* ff_adpcm_AdaptationTable[], ff_adpcm_AdaptCoeff1[], and ff_adpcm_AdaptCoeff2[] are from libsndfile */
const int16_t adpcm_AdaptationTable[] = {
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
};
/** Divided by 4 to fit in 8-bit integers */
const uint8_t adpcm_AdaptCoeff1[] = {
64, 128, 0, 48, 60, 115, 98
};
/** Divided by 4 to fit in 8-bit integers */
const int8_t adpcm_AdaptCoeff2[] = {
0, -64, 0, 16, 0, -52, -58
};
static const int av_clip_int16(int a)
{
if (a > 32767)
{
a = 32767;
}
else if (a < -32768)
{
a = -32768;
}
return a;
}
static int16_t adpcm_ms_expand_nibble(adpcm_block_info *block_info, int nibble)
{
int predictor;
predictor = (((block_info->sample1) * (block_info->coeff1)) + ((block_info->sample2) * (block_info->coeff2))) / 64;
predictor += ((nibble & 0x08)?(nibble - 0x10):(nibble)) * block_info->delta;
block_info->sample2 = block_info->sample1;
block_info->sample1 = av_clip_int16(predictor);
block_info->delta = (adpcm_AdaptationTable[(int)nibble] * block_info->delta) >> 8;
if (block_info->delta < 16) block_info->delta = 16;
if (block_info->delta > INT_MAX/768) {
printf("[adpcm]%s:%d idelta overflow\n", __FUNCTION__, __LINE__);
block_info->delta = INT_MAX/768;
}
return block_info->sample1;
}
int adpcm_decode_block(int16_t *output, uint8_t *input, adpcm_block_info *block_info)
{
uint8_t *read_ptr = RT_NULL;
uint32_t nb_samples = 0;
int n;
block_info->block_predictor = *input;
block_info->delta = (int16_t)input[2] << 8 | input[1] ;
block_info->sample1 = (int16_t)input[4] << 8 | input[3];
block_info->sample2 = (int16_t)input[6] << 8 | input[5];
block_info->nibbles_prt = &input[7];
read_ptr = block_info->nibbles_prt;
nb_samples = (ADPCM_BLOCK_SIZE-6)*2;
if (block_info->block_predictor > 6) {
printf("[adpcm]%s:%d ERROR: block_info->block_predictor = %d\n", __FUNCTION__, __LINE__,
block_info->block_predictor);
return -1;
}
block_info->coeff1 = adpcm_AdaptCoeff1[block_info->block_predictor];
block_info->coeff2 = adpcm_AdaptCoeff2[block_info->block_predictor];
*output++ = block_info->sample2;
*output++ = block_info->sample1;
for(n = (nb_samples - 2) / 2; n > 0; n--)
{
int byte = *read_ptr;
*output++ = adpcm_ms_expand_nibble(block_info, byte >> 4 );
*output++ = adpcm_ms_expand_nibble(block_info, byte & 0x0F);
read_ptr++;
}
return nb_samples*sizeof(int16_t);
}
在使用上也很方便,ADPCM 也是通过 WAV 容器封装,这部分的解析代码可以复用,只要定位到音频数据位置,接着不停从文件中读取、每次处理 block_size 字节,循环至文件结束即可,测试代码如下:
#define ADPCM_BLOCK_SIZE 1024
#define PLAY_ADPCM_READ_SIZE ADPCM_BLOCK_SIZE
#define PLAY_BUFFER_SIZE PLAY_ADPCM_READ_SIZE*4
int main()
{
...
// 打开文件、解析 WAV 容器
while (1)
{
memset(read_buffer, 0, PLAY_ADPCM_READ_SIZE);
memset(write_buffer, 0, PLAY_BUFFER_SIZE);
read_size = fread(read_buffer, 1, PLAY_ADPCM_READ_SIZE, fp);
if (read_size == 0)
break;
decode_byte = adpcm_decode_block((int16_t*)write_buffer, (uint8_t*)read_buffer, &b_info);
sound_device_write(dev, 0, write_buffer, decode_byte); // 解码后的 PCM 数据写入声卡设备播放
}
// 关闭文件、释放资源
...
}
ADPCM的编码和解码实现
ADPCM压缩算法
ADPCM(Adaptive Differential Pulse Code Modulation),是一种针对16bits(或8bits或者更高)声音波形数据的一种有损压缩算法,它将声音流中每次采样的16bit数据以4bit存储,所以压缩比1:4.而且压缩/解压缩算法非常简单,所以是一种低空间消耗,高质量高效率声音获得的好途径。保存声音的数据文件后缀名为.AUD的大多用ADPCM压缩。 ADPCM主要是针对连续的波形数据的,保存的是波形的变化情况,以达到描述整个波形的目的,由于它的编码和解码的过程却很简洁,列在后面,相信大家能够看懂。 8bits采样的声音人耳是可以勉强接受的,而16bit采样的声音可以算是高音质了。ADPCM算法却可以将每次采样得到的16bit数据压缩到4bit。需要注意的是,如果要压缩/解压缩得是立体声信号,采样时,声音信号是放在一起的,需要将两个声道分别处理。
ADPCM压缩过程
首先我们认为声音信号都是从零开始的,那么需要初始化两个变量
int index=0,prev_sample=0;
下面的循环将依次处理声音数据流,注意其中的getnextsample()应该得到一个16bit的采样数据,而outputdata()可以将计算出来的数据保存起来,程序中用到的step_table[],index_adjust[]附在后面:
int index=0,prev_sample:=0;
while (还有数据要处理)
{
cur_sample=getnextsample(); //得到当前的采样数据
delta=cur_sample-prev_sample; //计算出和上一个的增量
if (delta<0) delta=-delta,sb=8; //取绝对值
else sb = 0 ; // sb保存的是符号位
code = 4*delta / step_table[index]; //根据steptable[]得到一个0-7的值
if (code>7) code=7; //它描述了声音强度的变化量
index += index_adjust[code] ; //根据声音强度调整下次取steptable的序号
if (index<0) index=0; //便于下次得到更精确的变化量的描述
else if (index>88) index=88;
prev_sample=cur_sample;
outputode(code|sb); //加上符号位保存起来
}
ADPCM解压缩过程
接压缩实际是压缩的一个逆过程,同样其中的getnextcode()
应该得到一个编码,,而outputsample()
可以将解码出来的声音信号保存起来。这段代码同样使用了同一个的setp_table[]和index_adjust()附在后面:
int index=0,cur_sample=0;
while (还有数据要处理)
{
code=getnextcode(); //得到下一个数据
if ((code & 8) != 0) sb=1 else sb=0;
code&=7; //将code分离为数据和符号
delta = (step_table[index]*code)/4+step_table[index]/8; //后面加的一项是为了减少误差
if (sb==1) delta=-delta;
cur_sample+=delta; //计算出当前的波形数据
if (cur_sample>32767) output_sample(32767);
else if (cur_sample<-32768) output_sample(-32768);
else output_sample(cur_sample);
index+=index_adjust[code];
if (index<0) index=0;
if (index>88) index=88;
}
int index_adjust[8] = {-1,-1,-1,-1,2,4,6,8};
int step_table[89] =
{
7,8,9,10,11,12,13,14,16,17,19,21,23,25,28,31,34,37,41,45,
50,55,60,66,73,80,88,97,107,118,130,143,157,173,190,209,230,253,279,307,337,371,
408,449,494,544,598,658,724,796,876,963,1060,1166,1282,1411,1552,1707,1878,2066,
2272,2499,2749,3024,3327,3660,4026,4428,4871,5358,5894,6484,7132,7845,8630,9493,
10442,11487,12635,13899,15289,16818,18500,20350,22385,24623,27086,29794,32767
}